#pragma once
#include <sstream>
#include <eigen3/Eigen/Dense>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>

using namespace Eigen;

namespace spdlog_API{

    class ExceptionStream{
    private:
        std::stringstream m_Stream;
    public:
        ExceptionStream();
        ~ExceptionStream();
        void Flush();
        template<typename T> 
        void Append(const T& str){
            m_Stream<<str;
        }
        
    };

    class LogStream{
    private:
        std::shared_ptr<spdlog::logger> m_Logger;
        spdlog::level::level_enum m_Level;
        std::stringstream m_Stream;
    public:
        LogStream(std::shared_ptr<spdlog::logger> log, spdlog::level::level_enum level);
        ~LogStream();
        void Flush();
        template<typename T> 
        void Append(const T& str){
            m_Stream<<str;
        }

        static void logException(std::exception_ptr eptr,std::shared_ptr<spdlog::logger> log);
        static void logException(std::exception_ptr eptr);
        static std::shared_ptr<LogStream> logWithLevel(std::shared_ptr<spdlog::logger> log, spdlog::level::level_enum level);
        static std::shared_ptr<LogStream> logInfo(std::shared_ptr<spdlog::logger> log);
        static std::shared_ptr<LogStream> logError(std::shared_ptr<spdlog::logger> log);
        static std::shared_ptr<LogStream> logWarn(std::shared_ptr<spdlog::logger> log);
        static std::shared_ptr<LogStream> logDebug(std::shared_ptr<spdlog::logger> log);
        static std::shared_ptr<ExceptionStream> throwExeption();
        
    };
    inline LogStream& operator<<(LogStream& os,std::ostream& (*fun)(std::ostream&)){
        os.Flush();
        
        return os; 
    }
    template<typename T> 
    inline LogStream& operator<<(LogStream& os, const T& dt){
        os.Append(dt);
        
        return os;  
    } 

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os,std::ostream& (*fun)(std::ostream&)){
        os->Flush();
        
        return os; 
    }
    template<typename T> 
    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os, const T& dt){
        os->Append(dt);
        
        return os;  
    }

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os, const Vector3f dt){
        os->Append('[');os->Append(dt.x());os->Append(',');os->Append(dt.y());os->Append(',');os->Append(dt.z());os->Append(']');
        
        return os;  
    }

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os, const Vector3d dt){
        os->Append('[');os->Append(dt.x());os->Append(',');os->Append(dt.y());os->Append(',');os->Append(dt.z());os->Append(']');
        
        return os;  
    }

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os,const std::chrono::time_point<std::chrono::steady_clock, std::chrono::duration<float, std::ratio<1, 1000000000>>> dt){
        os->Append(std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(dt.time_since_epoch()).count());os->Append("ms");
        
        return os;  
    }

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os, const std::chrono::time_point<std::chrono::steady_clock,std::chrono::nanoseconds> dt){
        os->Append(std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(dt.time_since_epoch()).count());os->Append("ms");
        
        return os;  
    }

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os, 
        const std::chrono::time_point<std::chrono::steady_clock, std::chrono::duration<uint64_t, std::ratio<1, 1000000000>>> dt){
        os->Append(std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(dt.time_since_epoch()).count());os->Append("ms");
        
        return os;  
    }

    inline std::shared_ptr<LogStream> operator<<(std::shared_ptr<LogStream> os, const std::chrono::nanoseconds dt){
        os->Append(std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(dt).count());os->Append("ms");
        
        return os;  
    }


    
    inline ExceptionStream& operator<<(ExceptionStream& os,std::ostream& (*fun)(std::ostream&)){
        os.Flush();
        
        return os; 
    }
    template<typename T> 
    inline ExceptionStream& operator<<(ExceptionStream& os, const T& dt){
        os.Append(dt);
        
        return os;  
    } 

    inline std::shared_ptr<ExceptionStream> operator<<(std::shared_ptr<ExceptionStream> os,std::ostream& (*fun)(std::ostream&)){
        os->Flush();
        
        return os; 
    }
    template<typename T> 
    inline std::shared_ptr<ExceptionStream> operator<<(std::shared_ptr<ExceptionStream> os, const T& dt){
        os->Append(dt);
        
        return os;  
    } 
} //namespace spdlog_API
